﻿
/// <reference path="../BaseObject.js" />
/// <reference path="_namespace.js" />



/*****************************************************************************
Ajax library class definition. Kevin Robinson 2007.
Class: ajax.data.DataTable
/*****************************************************************************/

ajax.lang.addNamespace("ajax.data");

//-----------------------------------------------------------------------------
ajax.data.DataTable = function(win, data)
{                    
    ajax.data.DataTable.superConstructor.call(this);         
    this.window = win; // So we can support events
    this.data   = new Array();
    this.loaded = false;
    this.message = {method: "", data: []}; // Stores the last Web Sevice message. Contains "method" and "data".
    
    // Things the client MUST set
    this.primaryKey             = "ID";     // Used for merging data
    this.servicePath            = "";       // The Path of the Web Service used for getting and setting data.    
    
    // Get Data properties
    this.getDataFormat          = "Xml";    // The data format that is RETURNED from the web service    
    this.getDataMethod          = "POST";   // The method for server quieries
    
    // Set Data properties
    this.setDataFormat          = "Json";   // The data format we want to use to SEND data to the web service    
    this.updateMethodName       = "Update"; // The name of the Web Service method to call to Update a data row
    this.deleteMethodName       = "Delete"; // The name of the web service method to call to delete a data row
    this.deletedRowMarker       = "RowDeleted"; // The marker applied by the web service to a row that has been deleted.
        
        
    this.getEvent(this.window, "onUpdated");    // Fires whenever local data has been updated. Clients can use this to refresh themselves    
    this.getEvent(this.window, "onHttpError");  // Fires when an Http Transport error has occured
    this.getEvent(this.window, "onNoData");     // Fires when no data rows are returned from the server (can indicate a problem, depending on the request made)
    this.getEvent(this.window, "onDataEvent");  // Fires whenever one of the three events below fires. Clients can use this to display a Progress indicator.
    this.getEvent(this.window, "onGetData");    // Fires JUST BEFORE a loader executes a GetData on the web service. Clients can alter the loader params here.
    this.getEvent(this.window, "onSaveData");   // Fires JUST BEFORE a loader execute a SaveData on the web service.
    this.getEvent(this.window, "onDeleteData"); // Fires JUST BEFORE a loader executes a DeleteData on the web service.
    
    
    this.setData(data);           
}
//-----------------------------------------------------------------------------
ajax.lang.extend("ajax.data.DataTable", "ajax.BaseObject");
//-----------------------------------------------------------------------------

ajax.data.DataTable.prototype.select = function(criteria)
{// Returns an array of data rows that match the criteria, similar to a .NET DataView
 // The Rows returned are NOT copies but are references to the original rows, similar to an Index buffer in DirectX
    
    var ret = [];
    
    for (var i = 0; i < this.data.length; i++)
    {
        var row = this.data[i];
        var matched = true;
        for (var name in criteria)
        {
            if (criteria[name].compareCallback)
            {
                if ( !criteria[name].compareCallback(criteria[name].value, row[name]) )
                {
                    matched = false;
                    continue;
                }
            }
            else
            {
                if (!criteria[name].value == row[name])
                {
                    matched = false;
                    continue;
                }
            }
        }
        if (matched)
            ret.push(row);
    }
    return ret;
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.onDataEventHandler = function(req)
{
    this.onDataEvent.call(this, req);
}

//-----------------------------------------------------------------------------
// These can be overridden to provide custom functionality
ajax.data.DataTable.prototype.getData = function(errorCallback)
{        
    errorCallback = errorCallback || this.httpError;    // Direct error to caller method OR our own, not both.    
    var loader = new ajax.net.ContentLoader(this, this["setDataFrom" + this.getDataFormat + "Response"], errorCallback,
        this.servicePath, "POST", null, true);
        
    // Fire Event
    this.onGetData.call(this, loader);
    this.onDataEvent.call(this, loader);       
    loader.sendRequest();  
    
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.saveData = function(dataRow, errorCallback)
{        
    errorCallback = errorCallback || this.httpError;    // Direct error to caller method OR our own, not both.
    var serialized  = ajax.lang["to" + this.setDataFormat + "String"](dataRow, false); // Serialize but NOT functions
    var loader      = new ajax.net.ContentLoader(this, this["setDataFrom" + this.getDataFormat + "Response"], errorCallback,
        this.servicePath, "POST", null, true);
    loader.requestParams.push("Data=" + escape(serialized));
    loader.requestParams.push("Method=" + escape(this.updateMethodName));
    // Fire Event
    this.onSaveData.call(this, loader); 
    this.onDataEvent.call(this, loader); 
    loader.sendRequest();   
          
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.deleteData = function(dataRow, errorCallback)
{
    errorCallback = errorCallback || this.httpError;    // Direct error to caller method OR our own, not both.
    var serialized  = ajax.lang["to" + this.setDataFormat + "String"](dataRow, false); // Serialize but NOT functions
    var loader      = new ajax.net.ContentLoader(this, this["setDataFrom" + this.getDataFormat + "Response"], errorCallback,
        this.servicePath, "POST", null, true);       
    loader.requestParams.push("Data=" + escape(serialized));
    loader.requestParams.push("Method=" + escape(this.deleteMethodName));
    
    // Fire Event
    this.onDeleteData.call(this, loader);  
    this.onDataEvent.call(this, loader);     
    loader.sendRequest();    
}

//-----------------------------------------------------------------------------
// OVERRIDE THIS to provide custom row type
ajax.data.DataTable.prototype.newRow = function()
{// Creates a new default row
    return ajax.data.DataTable.newRow();
}





//-----------------------------------------------------------------------------
// TODO: This should be called "mergeData"??
// OVERRIDE to apply (refreshed or new) data back to the data array
ajax.data.DataTable.prototype.applyData = function(newData)
{
    // If data is null, just return
    if (!newData)
        return;              
    
    // If there are multiple rows, just replace the whole lot as its faster
    if (newData.length > 1)
    {
        this.data = newData;
        return;
    }
    
    // Just one row so update/delete or add
    var newRow = newData[0];
    if (!newRow)
        return;
    
    // Update accordingly if we already have this
    for (var i = 0; i < this.data.length; i++)
    {
        var row = this.data[i];
        if (row[this.primaryKey] == newRow[this.primaryKey])
        {
            // UPDATE NEEDED
            if (newRow[this.deletedRowMarker])
                this.data.remove(row); // Delete
            else
                this.data[i] = newRow; // Update
                
            return
        }
    }
    
    // Still here? It must be an insert, so add it at the end
    this.data.push(newRow);        
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.setDataFromJsonResponse = function(res)
{    
    this.message = res.responseText.toJsonObject();
    if (this.message) 
        this.setData(this.message.data);
    
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.setDataFromXmlResponse = function(res)
{                
    this.message = res.responseText.xmlToJsonObject();
    if (this.message)
        this.setData(this.message.data);    
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.setData = function(data)
{                    
    this.loaded = data ? true : false; 
    this.applyData(data);
    
    // Check for no data length
    if (data && data.length < 1)
    {
        // Clients may wish to know about no data
        this.onNoData.call(this);
        if (this.message.method == this.updateMethodName)
        {
            // so an update was done that returned no rows - definately an error
            // Simulate Http Response.
            
            var res = {status: 500, responseText: 'Record could not be located for updating.'};
            this.httpError(res);
            return;
        }
    }

    // we have data       
    this.onUpdated.call(this, data);

}


//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.httpError = function(res)
{   // This method is deprecated. Clients should pass in a bound pointer to an error callback
    // so each data error is only routed to one clicnt hander (not a dozen listeners who all decide to
    // display user dialogs).
    ajax.debug.Output.writeLn(res.responseText);
    this.onHttpError.call(this, res);
}



// ************* Treeview data conversion section ***************

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.treeViewDataRowCompareCallback = function(row, node)
{   // Returns true if the tree node passed matches the row passed.
    // Override this to provide a consistant method for matching a tree node to our data row type
    return false;
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.treeViewDataRowCreateItem = function(row)
{   // returns a new tree item from a row of this type
    // Override this to provide a consistant method for creating tree nodes from one of our data rows types.
    return  ajax.lang.safeNew("ajax.components.Treeview.Item", "", "", "", "", "", "");
}




//-----------------------------------------------------------------------------
ajax.data.DataTable.prototype.toString = function()
{
    return "ajax.data.DataTable";    
}

//-----------------------------------------------------------------------------
ajax.data.DataTable.cast = function(obj)
{///<returns type="ajax.data.DataTable"/>
    return obj;            
}


// Statics
//-----------------------------------------------------------------------------
ajax.data.DataTable.newRow = function()
{///<summary>Creates a new row with default values</summary>
 ///<returns type="ajax.data.DataTable.GenericRow"/>
    var row = new ajax.data.DataTable.GenericRow();
    return row;       
}
//-----------------------------------------------------------------------------
ajax.data.DataTable.startsWithCompare = function(sText, sMatchTo)
{ ///<summary>Matches only if the matchto Starts with sText</summary>
  ///<returns type="
    return ajax.data.DataTable.likeCompare(sText, sMatchTo, true);
}
//-----------------------------------------------------------------------------
ajax.data.DataTable.likeCompare = function(sText, sMatchTo, startsOnly)
{ ///<summary>matches if the sText appears anywhere in the matchto</summary>
  ///<returns type="string"/>
    var regStart = (startsOnly) ? "^" : "";
    
    if (ajax.lang.getType(sText) != "string" || ajax.lang.getType(sMatchTo) != "string")
        return false;
        
    // Backslash out out the nasty chars from the input text
    var sText   = sText.replace(/([^$A-Za-z0-9_\s])/gi, "\\$1");
    var regExp = new RegExp(regStart + sText, "i");
    
    return sMatchTo.match(regExp);
}
//-----------------------------------------------------------------------------
ajax.data.DataTable.exactCompare = function(sText, sMatchTo)
{///<returns type="boolean"/>
    return sText == sMatchTo;
}

//-----------------------------------------------------------------------------
// Inner Classes
ajax.data.DataTable.GenericRow = function()
{
}
//-----------------------------------------------------------------------------
ajax.data.DataTable.Criteria = function(value, compareCallback)
{                
    this.value              = value;
    this.compareCallback    = compareCallback;    
}
ajax.data.DataTable.Criteria.prototype = 
{
    value: null,
    compareCallback: null
};
//-----------------------------------------------------------------------------
ajax.data.DataTable.Criteria.cast = function(obj)
{///<returns type="ajax.data.DataTable.Criteria"/>
    return obj;            
}



